﻿using System;
using System.Text;
using Wizard.Functions;
using Wizard.Compiler.Parsers;

namespace Wizard.Compiler.Tokenizers
{
    using Wizard.Runtime;

    internal abstract class BaseTokenizer
    {
        internal int Column;
        internal string LowerCaseIdentifier;
        internal int Line;
        protected char mCurChar;
        internal SourcePos mSourcePos;
        internal TokenType mTokenType;
        internal StringBuilder ValueStringBuilder = new StringBuilder();

        protected internal abstract int GetUnaryPriority(TokenType type);
        protected internal abstract int GetPriority(IHasValue ValueLeft, TokenType tt);

        public BaseTokenizer(SourcePos sourcePos)
        {
            mSourcePos = sourcePos;
            mCurChar = (char)sourcePos.ReadChar();
            NextToken();
        }

        public TokenType TokenType
        {
            get { return mTokenType; }
        }

        public string ValueString
        {
            get { return ValueStringBuilder.ToString(); }
        }

        protected void NextChar()
        {
            mCurChar = (char)mSourcePos.ReadChar();
        }

        public void NextToken()
        {
            ValueStringBuilder.Length = 0;
            mTokenType = TokenType.none;
            LowerCaseIdentifier = null;
            while (true)
            {
                Line = mSourcePos.Line;
                Column = mSourcePos.Column;
                mTokenType = ParseCurChar();
                if (mTokenType != TokenType.none || mTokenType == TokenType.end_of_file) break;
            }
        }

        protected virtual TokenType ParseCurChar()
        {
            switch (mCurChar)
            {
                case ' ':
                case '\t':
                    NextChar();
                    return TokenType.none;
                //ignore

                case '\xFFFF':
                    return TokenType.end_of_file;

                case '\n':
                    NextChar();
                    return TokenType.end_of_line;

                case '\r':
                    NextChar();
                    if (mCurChar == '\n')
                    {
                        NextChar();
                    }
                    return TokenType.end_of_line;

                case '-': // asc 45
                case '–': // asc 8211
                    NextChar();
                    return TokenType.operator_minus;

                case '+':
                    NextChar();
                    return TokenType.operator_plus;

                case '*':
                    NextChar();
                    return TokenType.operator_mul;

                case '/':
                    NextChar();
                    switch (mCurChar)
                    {
                        case '/':
                            while (mCurChar >= 32) NextChar();
                            return TokenType.none; // single line // comment
                        case '*':
                            NextChar();
                            while (mCurChar > 0)
                            {
                                if (mCurChar == '*')
                                {
                                    NextChar();
                                    if (mCurChar == '/')
                                    {
                                        NextChar();
                                        break;
                                    }
                                }
                                else NextChar();
                            }
                            return TokenType.none; // multi line /* comment */
                        default:
                            return TokenType.operator_div;
                    }

                case ':':
                    NextChar();
                    if (mCurChar == '=')
                    {
                        NextChar();
                        return TokenType.colon_equal;
                    }
                    return TokenType.colon;

                case '%':
                    NextChar();
                    return TokenType.operator_percent;

                case '(':
                    NextChar();
                    return TokenType.open_parenthesis;

                case ')':
                    NextChar();
                    return TokenType.close_parenthesis;

                case '?':
                    NextChar();
                    return TokenType.question_mark;

                case '<':
                    NextChar();
                    if (mCurChar == '=')
                    {
                        NextChar();
                        return TokenType.operator_le;
                    }
                    if (mCurChar == '>')
                    {
                        NextChar();
                        return TokenType.operator_ne;
                    }
                    return TokenType.operator_lt;

                case '>':
                    NextChar();
                    if (mCurChar == '=')
                    {
                        NextChar();
                        return TokenType.operator_ge;
                    }
                    return TokenType.operator_gt;

                case ',':
                    NextChar();
                    return TokenType.comma;

                case '=':
                    NextChar();
                    return TokenType.operator_eq;

                case '.':
                    NextChar();
                    return TokenType.dot;

                case '"':
                    ParseText(true);
                    return TokenType.value_string;

                //case '#':
                //this.ParseDate();
                //  break;
                case '[':
                    NextChar();
                    return TokenType.open_bracket;

                //case '\\':
                //    //if (mSyntax == ParserSyntax.Vb)
                //    {
                //        this.NextChar();
                //        this.skipEndOfLineComment();
                //    }
                //    //else
                //    //{
                //    //    this.ParseIdentifier();
                //    //}
                //    break;

                case ']':
                    NextChar();
                    return TokenType.close_bracket;
                default:
                    if (mCurChar >= '0' && mCurChar <= '9')
                    {
                        ParseNumber();
                        return TokenType.value_number;
                    }
                    if (char.IsLetter(mCurChar))
                    {
                        return ParseIdentifier();
                    }
                    throw NewError("Unexpected character", null);
            }
            //            return eTokenType.value_identifier;
        }

        protected virtual void ParseDate()
        {
            //        this.NextChar();
            //        while (((((mCurChar >= '0') & (mCurChar <= '9')) | (mCurChar == '/')) | (mCurChar == ':')) | (mCurChar == ' '))
            //        {
            //            this.Value.Append(mCurChar);
            //            this.NextChar();
            //        }
            //        if (mCurChar != '#')
            //        {
            //            this.RaiseError("Invalid date should be #dd/mm/yyyy#", null);
            //        }
            //        else
            //        {
            //            this.NextChar();
            //        }
            //        return eTokenType.value_date;
        }

        protected virtual void parseEndOfLine(bool mandatory)
        {
            if ((mTokenType == TokenType.end_of_line) | (mTokenType == TokenType.end_of_file))
            {
                while (mTokenType == TokenType.end_of_line)
                {
                    NextToken();
                }
            }
            else if (mandatory)
            {
                throw NewError("End of line expected", null);
            }
        }

        protected virtual TokenType ParseIdentifier()
        {
            while ((mCurChar >= '0' && mCurChar <= '9')
                   || (mCurChar >= 'a' && mCurChar <= 'z')
                   || (mCurChar >= 'A' && mCurChar <= 'Z')
                   || (mCurChar >= 128 && mCurChar < '\xFFF')
                   || mCurChar == '_'
                   || mCurChar == '@')
            {
                ValueStringBuilder.Append(mCurChar);
                NextChar();
            }
            var word = ValueStringBuilder.ToString();
            var lowerCaseWord = word.ToLower();

            var result = GetInvalidIdentifiers(word, lowerCaseWord);

            LowerCaseIdentifier = (result == TokenType.identifier ? lowerCaseWord : null);
            return result;
        }

        protected virtual TokenType GetInvalidIdentifiers(string word, string lowerCaseWord)
        {
            // I put here the keywords which should work in all languages.
            switch (lowerCaseWord)
            {
                case "true":
                    return TokenType.value_true;
                case "false":
                    return TokenType.value_false;
            }
            return TokenType.identifier;
        }

        protected virtual TokenType ParseNumber()
        {
            while (mCurChar >= '0' && mCurChar <= '9')
            {
                ValueStringBuilder.Append(mCurChar);
                NextChar();
            }
            if (mCurChar == '.')
            {
                ValueStringBuilder.Append(mCurChar);
                NextChar();
                while (mCurChar >= '0' && mCurChar <= '9')
                {
                    ValueStringBuilder.Append(mCurChar);
                    NextChar();
                }
            }
            return TokenType.value_number;
        }

        protected virtual IHasValue ParseText(bool InQuote)
        {
            IHasValue curString = null;
            var OriginalChar = '\0';
            if (InQuote)
            {
                OriginalChar = mCurChar;
                NextChar();
            }
            while (mCurChar != '\0')
            {
                if (InQuote && mCurChar == OriginalChar)
                {
                    NextChar();
                    if (mCurChar == OriginalChar)
                    {
                        ValueStringBuilder.Append(mCurChar);
                        continue;
                    }
                    InQuote = false;
                    break;
                }
                if (mCurChar == '%')
                {
                    this.NextChar();
                    if (mCurChar == '[')
                    {
                        this.NextChar();
                        StringBuilder valueBefore = this.ValueStringBuilder;
                        if (valueBefore.Length > 0)
                        {
                            IHasValue opCodeBefore = null; // new Const<string>(valueBefore.ToString());
                            if (curString == null)
                            {
                                curString = opCodeBefore;
                            }
                            else
                            {
                                //curString = new opCodeBinary(this, curString, eTokenType.operator_concat, opCodeBefore);
                            }
                        }
                        this.ValueStringBuilder.Length = 0;
                        NextToken();
                        IHasValue subExpr = null;
                        try
                        {
                            subExpr = null; //mParser.ParseExpr(null, priority);
                            if (curString == null)
                            {
                                curString = subExpr;
                            }
                            else
                            {
                                curString = null; // new Concat(Utils.ToString(curString), Utils.ToString(subExpr));
                            }
                        }
                        catch (Exception exception1)
                        {
                            var ex = exception1;
                            ValueStringBuilder.Append("[Error " + ex.Message + "]");
                        }
                        ValueStringBuilder.Length = 0;
                    }
                    else
                    {
                        ValueStringBuilder.Append('%');
                    }
                }
                else
                {
                    ValueStringBuilder.Append(mCurChar);
                    NextChar();
                }
            }
            if (InQuote)
            {
                throw NewError("Incomplete string, missing " + OriginalChar + "; String started", null);
            }
            if (ValueStringBuilder.Length == 0)
            {
//                return curString ?? new Const<string>(string.Empty);
            }
//            IHasValue lastString = new Const<string>(ValueStringBuilder.ToString());
//            return curString == null ? lastString : null;
            return null;
        }

        protected virtual string RestOfLine()
        {
            ValueStringBuilder.Length = 0;
            while (mCurChar != '\r' && mCurChar != '\n' && mCurChar != '\0')
            {
                ValueStringBuilder.Append(mCurChar);
                NextChar();
            }
            return ValueStringBuilder.ToString();
        }

        protected virtual TokenType skipEndOfLineComment()
        {
            RestOfLine();
            return TokenType.end_of_line;
        }

        protected ParserException NewError(string msg, Exception ex)
        {
            if (ex is ParserException)
            {
                msg = msg + ". " + ex.Message;
            }
            else
            {
                msg = msg + "  at line " + Line + " column " + Column;
                if (ex != null)
                {
                    msg = msg + ". " + ex.Message;
                }
            }
            return new ParserException(msg, null, Line, Column);
        }
    }

}
//public abstract class BaseTokenizer
//{
//    private Token mToken;
//    private SourceReader mSrc;
//    private int mTokenLine, mTokenColumn;
//    StringBuilder mTextSb;
//    private bool mNewLine;

//    //private NextState[] mNextState;
//    //private SourceReader mSourceReader;
//    protected char mCurChar;

//    struct NextState
//    {
//        public Token Token;
//        public ProcessCharFunction ProcessCharFunc;
//    }

//    private NextState ReturnToken(Token token)
//    {
//        return new NextState() { Token = token };
//    }

//    private NextState UseFunction(ProcessCharFunction func)
//    {
//        return new NextState() { ProcessCharFunc = func };
//    }

//    delegate void ProcessCharFunction();

//    public BaseTokenizer(ISourceStream src)
//    {
//        mTextSb = new StringBuilder(260);
//        mSrc = new SourceReader(src);
//        ReadNextChar();
//        ReadNextToken();
//        //mLine = 1;
//    }

//    //public BaseTokenizer(SourceReader sourceReader)
//    //{
//    //mSourceReader = sourceReader;


//    //}

//    private void ProcessDigit()
//    {
//        mToken = Token.INTEGER;
//        ReadNextChar();
//    }

//    private void ProcessLetter()
//    {
//        mToken = Token.tok_identifier;
//        ReadNextChar();
//    }

//    private void ProcessHash()
//    {
//        //mToken = Token.Identifier;
//        ReadNextChar();
//    }

//    protected void SkipSpaces()
//    {
//        while (mCurChar != '\0')
//        {
//            switch (mCurChar)
//            {
//                case ' ':
//                case '\t':
//                    ReadNextChar();
//                    break; // ignore spaces
//                case '\n':
//                case '\r':
//                    mNewLine = true;
//                    ReadNextChar();
//                    break; // ignore spaces
//                default:
//                    return;
//            }
//        }
//    }

//    protected void ProcessChar()
//    {
//        SkipSpaces();

//        mTokenLine = mSrc.Line;
//        mTokenColumn = mSrc.Column;
//        switch (mCurChar)
//        {
//            case '\xFFFF':
//            case '\0':
//                mToken = Token.END_OF_FILE;
//                // no nextchar
//                break;
//            case ' ':
//            case '\t':
//                ReadNextChar();
//                break; // ignore spaces
//            case '\n':
//            case '\r':
//                mNewLine = true;
//                ReadNextChar();
//                break; // ignore spaces
//            case '<':
//                ReadNextChar();
//                if (mCurChar == '=')
//                {
//                    ReadNextChar();
//                    mToken = Token.op_less_or_equal;
//                }
//                else if (mCurChar == '<')
//                {
//                    ReadNextChar();
//                    mToken = Token.op_shift_left;
//                }
//                else if (mCurChar == '>')
//                {
//                    ReadNextChar();
//                    mToken = Token.op_different;
//                }
//                else mToken = Token.op_less_than;
//                break;
//            case '>':
//                ReadNextChar();
//                if (mCurChar == '=')
//                {
//                    ReadNextChar();
//                    mToken = Token.op_more_or_equal;
//                }
//                else if (mCurChar == '>')
//                {
//                    ReadNextChar();
//                    mToken = Token.op_shift_right;
//                }
//                else mToken = Token.op_more_than;
//                break;
//            case '=':
//                ReadNextChar();
//                mToken = Token.op_equal;
//                break;
//            case '(':
//                ReadNextChar();
//                mToken = Token.tok_open_parenthesis;
//                break;
//            case ')':
//                ReadNextChar();
//                mToken = Token.tok_close_parenthesis;
//                break;
//            case '+':
//                ReadNextChar();
//                mToken = Token.op_plus;
//                break;
//            case '-':
//                ReadNextChar();
//                mToken = Token.op_minus;
//                break;
//            case '*':
//                ReadNextChar();
//                if (mCurChar == '=')
//                {
//                    ReadNextChar();
//                    mToken = Token.op_mul_equal;
//                }
//                else mToken = Token.op_mul;
//                break;
//            case '"':
//                ParseString();
//                break;
//            case '/':
//                ReadNextChar();
//                switch (mCurChar)
//                {
//                    case '*':
//                        // this is a /@ @/ comment 
//                        ReadNextChar();
//                        ParseSlashStarComment();
//                        break;
//                    case '/':
//                        ReadNextChar();
//                        while (mCurChar != '\0')
//                        {
//                            ReadNextChar();
//                            if (mCurChar == '\n' || mCurChar == '\r') break;
//                        }
//                        break;
//                    case '=':
//                        ReadNextChar();
//                        mToken = Token.op_mul_equal;
//                        break;
//                    default:
//                        mToken = Token.op_div;
//                        break;
//                }
//                break;
//            case '.':
//                ReadNextChar();
//                mToken = Token.tok_dot;
//                break;
//            case ',':
//                ReadNextChar();
//                mToken = Token.tok_comma;
//                break;
//            case '#':
//                ReadNextChar();
//                break;

//            default:
//                if (char.IsLetter(mCurChar))
//                {
//                    ParseKeywordOrIdentifier();
//                }
//                else if (char.IsNumber(mCurChar))
//                {
//                    ParseNumber();
//                }
//                else
//                {
//                    RaiseError("Invalid character :" + mCurChar);
//                }
//                break;
//        }
//    }

//    public bool NewLine
//    {
//        get { return mNewLine; }
//    }

//    //public Token ReadNextToken()
//    //{
//    //    mToken = Token.tok_undefined;

//    //    do
//    //    {
//    //        ProcessChar();
//    //    } while (mToken == Token.tok_undefined);

//    //    return mToken;
//    //}

//    public Token ReadNextToken()
//    {
//        mNewLine = false;
//        mToken = Token.tok_undefined;
//        mTextSb.Length = 0;
//        while (mToken == Token.tok_undefined)
//        {
//            ProcessChar();
//        }
//        return mToken;
//    }

//    private void ParseNumber()
//    {
//        mTextSb.Length = 0;
//        while (char.IsDigit(mCurChar))
//        {
//            mTextSb.Append(mCurChar);
//            ReadNextChar();
//        }
//        if (mCurChar == '.')
//        {
//            mTextSb.Append(mCurChar);
//            ReadNextChar();
//            while (char.IsDigit(mCurChar))
//            {
//                mTextSb.Append(mCurChar);
//                ReadNextChar();
//            }
//            mToken = Token.tok_decimal;
//        }
//        else mToken = Token.tok_integer;
//    }

//    private void ParseKeywordOrIdentifier()
//    {
//        mTextSb.Length = 0;
//        while (char.IsLetterOrDigit(mCurChar))
//        {
//            mTextSb.Append(mCurChar);
//            ReadNextChar();
//        }

//        if (mTextSb.Length > 0 && mTextSb.Length < 50)
//        {
//            string text = mTextSb.ToString().ToUpper();
//            if (Enum.IsDefined(typeof(Token), text))
//            {
//                mToken = (Token)Enum.Parse(typeof(Token), text);
//            }
//            else mToken = Token.tok_identifier;
//        }
//        else mToken = Token.tok_identifier;
//    }

//    private void ParseSlashStarComment()
//    {
//        while (mCurChar != '\0')
//        {
//            if (mCurChar == '*')
//            {
//                ReadNextChar();
//                if (mCurChar == '/')
//                {
//                    ReadNextChar();
//                    return;
//                }
//            }
//            else if (mCurChar == '/')
//            {
//                ReadNextChar();
//                if (mCurChar == '*')
//                {
//                    ReadNextChar();
//                    // inner /* comment @/
//                    ParseSlashStarComment();
//                }
//            }
//            else ReadNextChar();
//        }
//        RaiseError("Uncompleted comment");
//    }

//    private void ParseString()
//    {
//        ReadNextChar();
//        mTextSb.Length = 0;
//        while (mCurChar != '\0')
//        {
//            if (mCurChar == '"')
//            {
//                ReadNextChar();
//                if (mCurChar == '"')
//                {
//                    mTextSb.Append('"');
//                    ReadNextChar();
//                }
//                else
//                {
//                    // we got a string
//                    mToken = Token.tok_string;
//                    return;
//                }
//            }
//            else if (mCurChar == '\\')
//            {
//                ReadNextChar();
//                switch (mCurChar)
//                {
//                    case 'n':
//                        mTextSb.Append('\n');
//                        break;
//                    case 'r':
//                        mTextSb.Append('\r');
//                        break;
//                    case 't':
//                        mTextSb.Append('\t');
//                        break;
//                    case '0':
//                        mTextSb.Append('\0');
//                        break;
//                    case 'x':
//                        //todo mTextSb.Append('\0');
//                        break;
//                    case 'u':
//                        //todo mTextSb.Append('\0');
//                        break;
//                }
//            }
//            else
//            {
//                mTextSb.Append(mCurChar);
//                ReadNextChar();
//            }
//        }
//        RaiseError("Unterminated string");
//    }

//    private char ReadNextChar()
//    {
//        mCurChar = mSrc.ReadNextChar();
//        return mCurChar;
//    }

//    public Token Token
//    {
//        get { return mToken; }
//    }

//    public string Text
//    {
//        get { return mTextSb.ToString(); }
//    }

//    internal void RaiseError(string message)
//    {
//        throw new SyntaxError(message + "\r\nLine:" + mTokenLine.ToString() + " Column:" + mTokenColumn.ToString());
//    }

//    internal void RaiseUnexpectedToken(string msg)
//    {
//        throw new UnexpectedToken("Unexpected token " + mToken.ToString() + "\r\n" + msg + "\r\nLine:" + mTokenLine.ToString() + " Column:" + mTokenColumn.ToString());
//    }


//}